#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

enum REDIR_FLAGS{
  NONE_REDIR,
  INPUT_REDIR,
  OUTPUT_REDIR,
  APPEND_REDIR
};

char cmd_line[1024]; //用于接收存储整条命令
char* cmd_param[32]; //将整条命令拆解成一个个参数
char env_buffer[64]; //环境变量缓冲区
int redir_flag = NONE_REDIR; 

char* CheckRedir(char **cmd_param, int sz){
  int i = 0;
  char *filepath = NULL;
  for(i = 0; i<sz; ++i)
  {
    if(strcmp(cmd_param[i], ">") == 0)
    {
      redir_flag = OUTPUT_REDIR; 
      filepath = cmd_param[i+1];
      cmd_param[i] = NULL;
      break;
    }
    else if(strcmp(cmd_param[i], ">>") == 0)
    {
      redir_flag = APPEND_REDIR; 
      filepath = cmd_param[i+1];
      cmd_param[i] = NULL;
      break;
    }
    else if(strcmp(cmd_param[i], "<") == 0)
    {
      redir_flag = INPUT_REDIR; 
      filepath = cmd_param[i+1];
      cmd_param[i] = NULL;
      break;
    }
  }
  return filepath;
}

//shell运行原理：父进程接收并解析命令，创建子进程执行命令，父进程等待。
int main(){
  //0.命令行解释器是常驻内存进程，不退出
  while(1)
  {
    //1.打印提示信息：[root@localhost myshell]#
    printf("[root@localhost myshell]# ");
    fflush(stdout);
    //sleep(1);
    //2.获取用户输入（包括指令和选项）："ls -a -l -i"
    memset(cmd_line, '\0', sizeof(cmd_line));    
    if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL) continue;
    if(strcmp(cmd_line, "\n") == 0) continue;
    cmd_line[strlen(cmd_line)-1] = '\0';
    //printf("%s\n", cmd_line);
    //3.命令行字符串解析："ls -a -l" --> "ls" "-a" "-l"
    cmd_param[0] = strtok(cmd_line, " ");
    int i = 1;
    while((cmd_param[i++] = strtok(NULL, " "))); //多加一层圆括号防止高亮

    //实现重定向功能：
    char *filepath = CheckRedir(cmd_param, i-1);

    //for(int i = 0; cmd_param[i]; ++i)
    //{
    //  printf("%s ",cmd_param[i]);
    //}
    //printf("\n");
    //4.内置命令：让父进程（shell）自己执行的指令，又叫内建命令
    //内建命令本身就是shell中的一个函数调用
    if(strcmp(cmd_param[0], "cd") == 0)
    {
      if(cmd_param[1] != NULL)
      chdir(cmd_param[1]); 
      continue;
    }
    if(strcmp(cmd_param[0], "export") == 0)
    {
      if(cmd_param[1] != NULL)
      {
        strcpy(env_buffer, cmd_param[1]);
        putenv(env_buffer);
      }
      continue;
    }
    //5.创建子进程执行命令：
    int id = fork();
    if(id == 0)
    {
      printf("I'm child process! pid:%d ppid:%d\n", getpid(), getppid());

      if(filepath != NULL)
      {
        int fd = -1;
        switch(redir_flag)
        {
          case INPUT_REDIR:
            printf("INPUT_REDIR\n");
            fd = open(filepath, O_RDONLY);
            dup2(fd, 0);
            break;
          case OUTPUT_REDIR:
            printf("OUTPUT_REDIR\n");
            fd = open(filepath, O_WRONLY | O_CREAT | O_TRUNC, 0666);
            dup2(fd, 1);
            break;
          case APPEND_REDIR:
            printf("APPEND_REDIR\n");
            fd = open(filepath, O_WRONLY | O_CREAT | O_APPEND, 0666);
            dup2(fd, 1);
            break;
          default:
            break;
        }
      }
      execvp(cmd_param[0], cmd_param);
      exit(1);
    }

    int status = 0;
    int ret = waitpid(-1, &status, 0);
    if(ret > 0)
    {
      if(WIFEXITED(status))
      {
        printf("normal exit! child_pid:%d exit_code:%d\n", ret, WEXITSTATUS(status));
      }
      else
      {
        printf("abnormal exit! child_pid:%d exit_signal:%d\n", ret, status&0x7F);
      }
    }
    else if(ret < 0)
    {
      printf("Waiting failed!\n");
    }
    printf("------------------------------------------------------------------\n");
  }
}
